package mars.ns.stat

import scala.collection.Map
import scala.collection.mutable.HashMap
import mars.ns.HasState
import mars.ns.core.Network
import mars.ns.core.Link
import mars.ns.core.Node
import mars.ns.core.LinkType
import mars.ns.listeners.SimulationAdapter
import java.io.FileWriter
import java.io.IOException


object Statistics {
    
    /**
     * Gathering the statistics
     */
    def getNbOfNodes[T <: Node] (net: Network[T], iteration: Int): DataRecord[Integer] = {
        return new DataRecord("nbOfNodes", net.getNodes.size)
    }
    
    def getNbOfSubGraphs[T <: Node] (net: Network[T], iteration: Int): DataRecord[Int] = {
        return new DataRecord("nbOfSubGraphs", net.getComponents.size)
    }
    
    def isConnected[T <: Node] (net: Network[T], iteration: Int) : DataRecord[Boolean] = {
        return new DataRecord("connected", (net.getComponents.size == 1))
    }
    
    def getNbOfNodesOfSameDegreeMap[T <: Node] (net: Network[T], iteration: Int): DataRecord[Map[Int, Int]] = {
        var degreeMap = new HashMap[Int, Int]
        for (node: Node <- net.getNodes){
    				var x = degreeMap.get(node.getLinks.size) match {
    				    case Some(n) => n
    				    case None => 0
    				}
    				x += 1
    				//The key says what the degree is, the value how much nodes there are with that degree
    				degreeMap.put(node.getLinks.size, x)
    	}
        return new DataRecord("nbOfNodesOfSameDegreeMap", degreeMap)
    }
    
    def getSizeLargestComponent[T <: Node] (net: Network[T], iteration: Int): DataRecord[Int] = {
    	var sizeLargestComponent: Int = 0;
        for (network <- net.getComponents())
	    		if(sizeLargestComponent < network.getNodes().size)
	    			sizeLargestComponent = network.getNodes().size;
        return new DataRecord("sizeLargestComponent", sizeLargestComponent)
    }
    
}

class Statistics[T <: Node](private var gatheringInterval: Int, private var data: List[(Int, List[DataRecord[_]])]) extends SimulationAdapter[T] { 
  
	def this(interval: Int) = this(interval, Nil)
	def this() = this(1, Nil)
	private var nbOfIterations : Int = 0;
    
    /**
     * hooks for gathering statistics data, they accept a network and an int which is the current iteration number
     * and they return a DataRecord
     */
    private var gatherDataHooks: List[(Network[T], Int) => DataRecord[_]] = Nil
    
    addGatherDataHook(Statistics.getNbOfNodes[T])
    addGatherDataHook(Statistics.getNbOfSubGraphs[T])
    addGatherDataHook(Statistics.isConnected[T])
    addGatherDataHook(Statistics.getNbOfNodesOfSameDegreeMap[T])
    addGatherDataHook(Statistics.getSizeLargestComponent[T])
    
    def getGatheredData: List[(Int, List[DataRecord[_]])] = data
    
    /**
     * Get the data collected on the specified iteration for the specified key
     */
    def getValue[Type](iteration: Int, key: String): Type = {
        for( (iter: Int, iterationData: List[DataRecord[_]]) <- getGatheredData) {
            if(iter == iteration) {
            	for(data: Any <- iterationData) {
            		data match {
                		case dr: DataRecord[Type] if dr.key.equals(key) => return dr.value
                		case _ => //continue
            		}
            	}
            	throw new IllegalArgumentException("No data for key [" + key + "] was gathered")
            }
        }
        throw new IllegalArgumentException("No data was saved on iteration " + iteration)
    }
    
    def getDataForIteration(iteration: Int): List[DataRecord[_]] = {
    	for( (iter: Int, iterationData: List[DataRecord[_]]) <- getGatheredData) {
    		if(iter == iteration) {
    			return iterationData
    		}
    	}
    	throw new IllegalArgumentException("No data was saved on iteration " + iteration)
    }
    
    def setGatheringInterval(interval: Int) = {
        if(interval < 1)
            throw new IllegalArgumentException("The interval cannot be smaller than 1");
        gatheringInterval = interval
    }
    
    def getGatheringInterval = gatheringInterval
    
    def addGatherDataHook(hook: (Network[T], Int) => DataRecord[_]) = {
        gatherDataHooks ::= hook
    }
    
    def removeGatherDataHook(hook: (Network[T], Int) => DataRecord[_]) = {
        gatherDataHooks = gatherDataHooks filter((arg: (Network[T], Int) => DataRecord[_]) => arg != hook) 
    }
    
    override def update(net: Network[T], iteration: Int) = {
    	if(((iteration % gatheringInterval) == 0)) {        
    		var iterationData: List[DataRecord[_]] = Nil  
    		
    		for(hook <- gatherDataHooks) {
    			var data: DataRecord[_] = hook(net, iteration)
    		    iterationData ::= data
    		}    		
    		
    		data ::= (iteration, iterationData)
    	}
    	nbOfIterations += 1;
    } 
    
    /**
     * This creates a csv file, which than can be read in R with the "rScript" script
     */
    def generateCsvFile(sFileName: String, key: String)
	{
		    var writer: FileWriter = new FileWriter(sFileName);
	 
		    //Schrijf de eerste regel, de namen van de x-as en y-as
		    var s :String = ("xval, yval\n");
		    var value : Any = getTheType(key);

		    /**
		     * Based on the type, we call a different csv generator
		     */
		    value match {
		    	case _:Boolean => s += genBoolCsv(key);
		    	case _:Int => s += genIntCsv(key);
		  		case _:HashMap[Int, Int] => throw new IllegalArgumentException("Cannot print all the iteration of the number of nodes of the same degree on one plot. Use the other function where you can pass an iteration.");
	  			case _:Any => throw new IllegalArgumentException("Cannot match the correct class!");
	  		}  
		    writer.append(s);
		    writer.flush();
		    writer.close();
	}
    
    /**
     * To plot a map, we need an iteration, because we cannot plot all the iterations of a map on just one plot, it would get very crowded.
     */
    def generateCsvFile(sFileName: String, key: String, iteration: Int){
         
        if (iteration <= nbOfIterations){
	        var writer: FileWriter = new FileWriter(sFileName);
		 
		    //Schrijf de eerste regel, de namen van de x-as en y-as
	    	var s :String = ("xval, yval\n");
	        var value : Any = getTheType(key);
	        
	        /**
		     * Based on the type, we call a different csv generator
		     */
			value match {
		  		case _:HashMap[Int, Int] => s += genMapCsv(key,iteration);
	  			case _:Any => throw new IllegalArgumentException("Cannot match the correct class!");
	  		}
			
		    writer.append(s);
	        writer.flush();
		    writer.close();
        }
        else
            throw new IllegalArgumentException("This iteration number does not exist!");
	}
    
    /**
     * generate csv for type boolean
     */
    private def genBoolCsv(key: String): String = {
        
        var s :String = "";
		var i: Int = 1;
		    
		    while (i != nbOfIterations + 1){
		  	    try{
		   	    	var value = getValue[Boolean](i, key);		    	        
		   	    	s += (i.toString() + ",");
	    	    	if (value)
	    	    		s += "1\n";
		    	   	else
		        		s += "0\n";
		   	    	
		   	    }catch{
		   	        case e: IllegalArgumentException => System.out.println(e.getMessage());
		   	    }
	    	    i += 1;
		    }
        return s;
    }
    
    /**
     * generate csv vor type int
     */
    private def genIntCsv(key: String): String = {
       
        var s :String = "";
		var i: Int = 1;
        
        while (i != nbOfIterations + 1){
		    try{
			    var value = getValue[Int](i, key);
	    		
	    	    s += (i.toString()+ "," + value.toString() + "\n")		    		
		  		}catch{
	    	        case e: IllegalArgumentException => System.out.println(e.getMessage());
	    	    }
		  		i += 1;
		    } 
        return s;
    }
    
    /**
     * generate csv for type map
     */
     private def genMapCsv(key: String, iteration: Int): String = {
         var s :String = "";

         try{
		    	var value = getValue[Map[Int, Int]](iteration, key);
		    	for(i: Int <- value.keySet){
		    		s += (i.toString() + ",");	
	    			var x = value.get(i) match {
		    			case Some(n) => n
		    			case None => 0
		    		}
	    			s += (x.toString() + "\n");
		    	}
		  	}catch{
		  		case e: IllegalArgumentException => System.out.println(e.getMessage());
		  	}
         return s;
     }
     
     /**
      * get the correct type based on the key
      * either boolean, int or map.
      */
     private def getTheType(key: String):Any ={
    	var i: Int = 1;
         
    	while (i != nbOfIterations + 1){
		  	    try{
		  	    	var value = getValue[Any](i, key);
		  	    	return value;
		  	    }catch{
		  	    	case e: IllegalArgumentException => ;
	    	    }
		  	    i += 1;
		    }
     }
      
    
}